<?php
/**
 * Nucleus: PHP/MySQL Weblog CMS (http://nucleuscms.org/)
 * Copyright (C) 2002-2012 The Nucleus Group
 *
 * Skin/template import/export classes
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * (see nucleus/documentation/index.html#license for more info)
 */

/**
 * This class contains two classes that can be used for importing and
 * exporting Nucleus skins: SKINIMPORT and SKINEXPORT
 *
 * @license http://nucleuscms.org/license.txt GNU General Public License
 * @copyright Copyright (C) 2002-2012 The Nucleus Group
 * @version $Id: skinableSkinie.php 163 2012-02-13 04:17:40Z shizuki17xx $
 */

/**
 * SKINIMPORT class
 * Used for importing Nucleus skins/templates
 */
class skinableSKINIMPORT
{

// {{{ properties

/**
 * private var
 *
 * @name debug
 * @var int(bool)
 */
private $debug;

/**
 * public var
 * Skin file meta data
 *
 * @name meta
 * @var array
 */
public $meta;

/**
 * public var
 * Skins into skin-file
 *
 * @name skins
 * @var array
 */
public $skins;

/**
 * public var
 * Templates into skin-file
 *
 * @name templates
 * @var array
 */
public $templates;

/**
 * public var
 * Import skins/templates data
 *
 * @name import
 * @var array
 */
public $import;

/**
 * public var
 * Skin-file infomation
 *
 * @name info
 * @var string
 */
public $info;

// }}}
// {{{ functions

    // {{{ cnstract()

    /**
     * Constructor
     * Initialize properties
     */
    public function __cnstract()
    {
        global $CONF;
        // disable magic_quotes_runtime if it's turned on
        ini_set('magic_quotes_runtime', 0);
        // Set debug-mode when $CONF['DebugVars'] is set(SYSTEM conf.).
        if (isset($CONF['DebugVars']) && !empty($CONF['DebugVars'])) {
            $this->debug = 1;
        }
        self::reset();
    }

    // }}}
    // {{{ reset()

    /**
     * Reset properties
     */
    public function reset()
    {
        $this->meta      = array();
        $this->skins     = array();
        $this->templates = array();
        $this->import    = array();
        $this->info      = '';
    }

    // }}}
    // {{{ readFile($filename, $metaOnly = 0)

    /**
     * Reads an XML file into memory
     *
     * @param string File name
     * @param int(bool) Set to 1 when only the metadata needs to be read (optional, default 0)
     */
    public function readFile($filename, $metaOnly = 0)
    {
        //echo $filename;
        if (!@file_exists($filename) ) {
            return _SKINIE_ERROR_FAILEDOPEN_FILEURL;
        }
        self::_readSkinFile($filename, $metaOnly);
    }

    // }}}
    // {{{ _readSkinFile($filename, $metaOnly)

    /**
     * Reads an XML file into memory
     *
     * @param string File name
     * @param int(bool) Set to 1 when only the metadata needs to be read (optional, default 0)
     */
    private function _readSkinFile($filename, $metaOnly)
    {
        $xmlContent = file_get_contents($filename);
        if (extension_loaded('mbstring') || defined('MB_CASE_UPPER')) {
            $set = array(
                'ASCII',
                'JIS',
                'SJIS',
                'EUC-JP',
                'UTF-8'
            );
            mb_detect_order($set);
            $charset = mb_detect_encoding($xmlContent);
            mb_convert_variables('UTF-8', $charset, $xml);
        }
        $xml = new SimpleXMLElement($xmlContent, LIBXML_NOCDATA);
        self::_getSkinMetaData($xml->xpath('/nucleusskin/meta/*'));
//*/
//		if (isset($metaOnly) && !empty($metaOnly)) {
//			return;
//		}
        self::_getNodeArray($xml->skin, 'skins');
        self::_getNodeArray($xml->template, 'templates');
        $this->import['skins']     = $this->skins;
        $this->import['templates'] = $this->templates;
    }

    // }}}
    // {{{ _readSkinFile($metaNodes)

    /**
     * Get skin-file's meta infomations
     *
     * @param object SimpleXMLElement object
     */
    private function _getSkinMetaData($metaNodes)
    {
        $meta = array();
        foreach ($metaNodes as $node) {
            $name = $node->getName();
            if ($name == 'info') {
                $meta[$name] = (string)$node;
                $this->info  = (string)$node;
            } else {
                $meta[$name][] = (string)$node['name'];
            }
        }
        $this->import['meta'] = $this->meta = $meta;
    }

    // }}}
    // {{{ _getNodeArray($nodes, $type)

    /**
     * Get skins/templates main data
     *
     * @param object SimpleXMLElement object
     * @param string Nodes type('skins' or templates').
     */
    private function _getNodeArray($nodes, $type)
    {
        $temp = array();
        foreach ($nodes as $node) {
            $name = (string)$node['name'];
            $temp[$name]['description'] = (string)$node->description;
            if ($type == 'skins') {
                $temp[$name]['type']          = (string)$node['type'];
                $temp[$name]['includeMode']   = (string)$node['includeMode'];
                $temp[$name]['includePrefix'] = (string)$node['includePrefix'];
            }
            foreach ($node as $part) {
                if ($part->getName() != 'part') {
                    continue;
                }
                $temp[$name]['parts'][(string)$part['name']] = (string)$part;
            }
        }
        $this->import[$type] = $this->$type = $temp;
    }

    // }}}
    // {{{ getSkinNames()

    /**
     * The list of skin names
     *
     * @return array import skin names
     */
    public function getSkinNames()
    {
        return array_keys($this->skins);
    }

    // }}}
    // {{{ getTemplateNames()

    /**
     * The list of template names
     *
     * @return array import template names
     */
    public function getTemplateNames()
    {
        return array_keys($this->templates);
    }

    // }}}
    // {{{ getInfo()

    /**
     * The extra information included in the XML file
     *
     * @return string skin-file extra infomation
     */
    public function getInfo()
    {
        return $this->info;
    }

    // }}}
    // {{{ checkSkinNameClashes()

    /**
     * Checking for skin-names conflicts.
     *
     * @return array conflict names
     */
    public function checkSkinNameClashes()
    {
        return self::checkConfricts('skins');
    }

    // }}}
    // {{{ checkTemplateNameClashes()

    /**
     * Checking for template-names conflicts.
     *
     * @return array conflict names
     */
    public function checkTemplateNameClashes()
    {
        return self::checkConfricts('templates');
    }

    // }}}
    // {{{ checkConfricts($type)

    /**
     * Checking for skin/template-names conflicts.
     *
     * @param string 'skin' or 'template'
     * @return array conflict names
     */
    public function checkConfricts($type)
    {
/*    	if (!class_exists('skinableSKIN')) {
    		require_once (dirname(__FILE__) . '/skinableSKIN.php');
    	}
    	if (!class_exists('skinableTEMPLATE')) {
    		require_once (dirname(__FILE__) . '/skinableTEMPLATE.php');
    	}*/
        $confricts = array();
        foreach ($this->$type as $name => $data) {
            $confrict = ($type == 'skins') ? (skinableSKIN::exists($name)) : (skinableTEMPLATE::exists($name));
            if ($confrict) {
                $confricts[] = $name;
            }
        }
        return $confricts;
    }

    // }}}
    // {{{ searchForCandidates($dir)

    /**
     * Detect importable XML files
     *
     * @param string search directory
     * @return array list of detected sub-directories into skin-files.
     */
	static public function searchForCandidates($dir)
	{
		$candidates = array();
		
		$dirhandle = opendir($dir);
		while ( $filename = readdir($dirhandle) )
		{
			if ( @is_dir($dir . $filename) && ($filename != '.') && ($filename != '..') )
			{
				$xml_file = $dir . $filename . '/skinbackup.xml';
				if ( file_exists($xml_file) && is_readable($xml_file) )
				{
					//$xml_file;
					$candidates[$filename] = $filename;
				}
				
				// backwards compatibility
				$xml_file = $dir . $filename . '/skindata.xml';
				if ( file_exists($xml_file) && is_readable($xml_file) )
				{
					//$xml_file;
					$candidates[$filename] = $filename;
				}
			}
		}
		closedir($dirhandle);
		return $candidates;
	}
/*    static public function searchForCandidates($dir)
    {
        $candidates = array();
        foreach (glob($dir.'*//*{skinbackup.xml,skindata.xml}', GLOB_BRACE) as $filename) {
            $filename = str_replace($dir, '', $filename);
            list($filename, $trush) = explode('/', $filename);
            $candidates[$filename]  = $filename;
        }
        return $candidates;
    }

    // }}}
    // {{{ writeToDatabase($allowOverwrite = 0)

    /**
     * Insert the skins/templates to the database
     *
     * @param int(bool) set to 1 when allowed to overwrite existing skins with the same name
     */
    function writeToDatabase($allowOverwrite = 0)
    {
        $existingSkins     = $this->checkSkinNameClashes();
        $existingTemplates = $this->checkTemplateNameClashes();
        $clashDetect       = ((sizeof($existingSkins) > 0) || (sizeof($existingTemplates) > 0));
        // if not allowed to overwrite, check if any nameclashes exists
        if (!$allowOverwrite && $clashDetect) {
            return _SKINIE_NAME_CLASHES_DETECTED;
        }
        // Insert the skins
        foreach ($this->skins as $skinName => $data) {
            // 1. if exists:     delete all part data, update desc data
            //    if not exists: create desc
            if (in_array($skinName, $existingSkins)) {
                $skinObj = skinableSKIN::createFromName($skinName);
                // delete all parts of the skin
                $skinObj->deleteAllParts();
                // update general info
                $skinObj->updateGeneralInfo(
                    $skinName,
                    $data['description'],
                    $data['type'],
                    $data['includeMode'],
                    $data['includePrefix']
                );
            } else {
                $skinid = skinableSKIN::createNew(
                    $skinName,
                    $data['description'],
                    $data['type'],
                    $data['includeMode'],
                    $data['includePrefix']
                );
                $skinObj = new skinableSKIN($skinid);
            }
            // 2. add parts
            foreach ($data['parts'] as $partName => $partContent) {
                $skinObj->update($partName, $partContent);
            }
        }
        // Insert the templates
        foreach ($this->templates as $templateName => $data) {
            // 1. if exists:     delete all part data, update desc data
            //    if not exists: create desc
            if (in_array($templateName, $existingTemplates)) {
                $templateObj = skinableTEMPLATE::createFromName($templateName);
                // delete all parts of the template
                $templateObj->deleteAllParts();
                // update general info
                $templateObj->updateGeneralInfo($templateName, $data['description']);
            } else {
                $templateid = skinableTEMPLATE::createNew($templateName, $data['description']);
                $templateObj = new skinableTEMPLATE($templateid);
            }
            // 2. add parts
            foreach ($data['parts'] as $partName => $partContent) {
                $templateObj->update($partName, $partContent);
            }
        }
    }

    // }}}

// }}}
}

/**
 * SKINEXPORT class
 * Used for exporting Nucleus skins/templates
 */
class skinableSKINEXPORT
{

// {{{ properties

/**
 * private var
 *
 * @name debug
 * @var int(bool)
 */
private $debug;

/**
 * public var
 *
 * @name meta
 * @var array
 */
public $meta;

/**
 * public var
 *
 * @name skins
 * @var array
 */
public $skins;

/**
 * public var
 *
 * @name templates
 * @var array
 */
public $templates;

/**
 * public var
 *
 * @name export
 * @var array
 */
public $export;

/**
 * public var
 *
 * @name info
 * @var string
 */
public $info;

/**
 * public var
 *
 * @name dom
 * @var object DOMDocument Object
 */
public $dom;

// }}}
// {{{ functions

    // {{{ __cnstract()

    /**
     * Constructor
     * Initialize properties
     */
    public function __cnstract()
    {
        global $CONF;
        if (isset($CONF['DebugVars']) && !empty($CONF['DebugVars'])) {
            $this->debug = 1;
        }
        $this->meta      = '';
        $this->skins     = array();
        $this->templates = array();
        $this->export    = array();
        $this->info      = '';
    }

    // }}}
    // {{{ setInfo($info)

    /**
     * Sets skin-file extra infomation
     *
     * @param string skin-file extra infomation
     */
    public function setInfo($info)
    {
        $this->info = $info;
    }

    // }}}
    // {{{ addTemplate($id)

    /**
     * Add export template data
     *
     * @param int template ID
     * @return int(bool) false when no such ID exists
     */
    public function addTemplate($id)
    {
        $id = intval($id);
        if (!skinableTEMPLATE::existsID($id)) {
            return 0;
        }
        $name = skinableTEMPLATE::getNameFromId($id);
        $this->templates[$name] = array(
                'description' => skinableTEMPLATE::getDesc($id),
                'parts'       => skinableTEMPLATE::read($name)
        );
    }

    // }}}
    // {{{ addSkin($id)

    /**
     * Add export skin data
     *
     * @param int skin ID
     * @return int(bool) false when no such ID exists
     */
    public function addSkin($id)
    {
        $id = intval($id);
        if (!skinableSKIN::existsID($id)) {
            return 0;
        }
        $skin  = new skinableSKIN($id);
        $this->skins[$skin->name] = array(
            'description'   => $skin->description,
            'type'          => $skin->contentType,
            'includeMode'   => $skin->includeMode,
            'includePrefix' => $skin->includePrefix
        );
        $parts = $skin->getFriendlyNames();
        ksort($parts);
        foreach ($parts as $name => $data) {
            $this->skins[$skin->name]['parts'][$name] = $skin->getContent($name);
        }
    }

    // }}}
    // {{{ createMetaAttr($data, $type)

    /**
     * Create DOM element <meta /> and inner elements
     *
     * @param array export skin/template data
     * @param string data type 'skin' or 'template'
     */
    private function createMetaAttr($data, $type)
    {
        foreach($data as $key => $value) {
            $elem = $this->dom->createElement($type);
            $attr = $this->dom->createAttribute('name');
            $name = $this->dom->createTextNode($key);
            $attr->appendChild($name);
            $elem->appendChild($attr);
            $this->meta->appendChild($elem);
        }
    }

    // }}}
    // {{{ createSkinTemplateNode($data, $type)

    /**
     * Create DOM element <skin />/<template /> and inner elements
     *
     * @param array export skin/template data
     * @param string data type 'skin' or 'template'
     */
    private function createSkinTemplateNode($data, $type)
    {
        foreach($data as $node => $nodeData) {
            $elem = $this->dom->createElement($type);
            $desc = $this->dom->createElement('description');
            $text = $this->dom->createCDATASection($nodeData['description']);
            $desc->appendChild($text);
            $elem->appendChild($desc);
            $attr = $this->dom->createAttribute('name');
            $name = $this->dom->createTextNode($node);
            $attr->appendChild($name);
            $elem->appendChild($attr);
            foreach ($nodeData as $key => $value) {
                if ($key == 'description' || $key == 'parts') {
                    continue;
                }
                $attr = $this->dom->createAttribute($key);
                $elem->appendChild($attr);
                $name = $this->dom->createTextNode($value);
                $attr->appendChild($name);
            }
            foreach ($nodeData['parts'] as $key => $value) {
                if (empty($value)) {
                    continue;
                }
                $part = $this->dom->createElement('part');
                $attr = $this->dom->createAttribute('name');
                $part->appendChild($attr);
                $name = $this->dom->createTextNode($key);
                $attr->appendChild($name);
                $data = $this->dom->createCDATASection($value);
                $part->appendChild($data);
                $elem->appendChild($part);
            }
            $this->export->appendChild($elem);
        }
    }

    // }}}
    // {{{ createSkinTemplateNode($data, $type)

    /**
     * Create DOM element <skin />/<template /> and inner elements
     *
     * @param int(bool) set to 0 if you don't want to send out headers
     */
    public function export($setHeaders = 1)
    {
        if ($setHeaders) {
            header('Content-Type: text/xml');
            header('Content-Disposition: attachment; filename="skinbackup.xml"');
            header('Expires: 0');
            header('Pragma: no-cache');
        }
        $this->dom = new DOMDocument();
        $this->dom->formatOutput = true;
        $this->export = $this->dom->createElement('nucleusskin');
        $this->meta   = $this->dom->createElement('meta');
        if (extension_loaded('mbstring') || defined('MB_CASE_UPPER')) {
            mb_convert_variables('UTF-8', _CHARSET, $this->skins);
            mb_convert_variables('UTF-8', _CHARSET, $this->templates);
        }
        if (is_array($this->skins) && count($this->skins) > 0) {
            self::createMetaAttr($this->skins, 'skin');
        }
        if (is_array($this->templates) && count($this->templates) > 0) {
            self::createMetaAttr($this->templates, 'template');
        }
        if (isset($this->info) && !empty($this->info)) {
            if (extension_loaded('mbstring') || defined('MB_CASE_UPPER')) {
                $this->info = mb_convert_encoding($this->info, 'UTF-8', _CHARSET);
            }
            $elem = $this->dom->createElement('info');
            $data = $this->dom->createCDATASection($this->info);
            $elem->appendChild($data);
            $this->meta->appendChild($elem);
        }
        $this->export->appendChild($this->meta);
        if (is_array($this->skins) && count($this->skins) > 0) {
            self::createSkinTemplateNode($this->skins, 'skin');
        }
        if (is_array($this->templates) && count($this->templates) > 0) {
            self::createSkinTemplateNode($this->templates, 'template');
        }
        $this->dom->appendChild($this->export);
        echo $this->dom->saveXML();
    }

    // }}}

// }}}

}

/*
$skin = './skins/default/skinbackup.xml';
$skinimport = new SKINIMPORT;
$skinimport->readFile($skin);
//mb_convert_variables('EUC-JP', 'UTF-8', $skinimport->templates);
print_r($skinimport->templates);/*
$export = $skinimport->import;
$skinexport = new SKINEXPORT;
$skinexport->setInfo($export['meta']['info']);
$skinexport->addTemplate(0);
$skinexport->addSkin(0);
$skinexport->export(0);

//*/



